from ..log import log_gui
from dynamicposition import DynamicPosition
from dynamicposition import LeftScreenEdge
from dynamicposition import RightScreenEdge
from dynamicposition import TopScreenEdge
from dynamicposition import BottomScreenEdge
from dynamicposition import ScreenWidth
from dynamicposition import ScreenHeight
from dynamicposition import FormHeight

class Unimplemented(Exception): pass
class Unresolved(Exception): pass
class AlreadyComputingHeight(Exception): pass

class BaseRelativeLayout:
  def __init__(self):
    self.dp_items = []

  def SetPositions(self):
    for dp_item in self.dp_items:
      item = dp_item.GetItem()
      if item:
        item.SetPosition(dp_item.GetInnerLeft(), dp_item.GetInnerTop())

  def AddItem(self, dp):
    self.dp_items.append(dp)

  def GetWidth(self):
    raise Unimplemented()

  def GetHeight(self):
    raise Unimplemented()

  def GetItems(self):
    """Return value may include some Nones where there are containers."""
    return [dp_item.GetItem() for dp_item in self.dp_items]

  def CanComputeWidth(self):
    raise Unimplemented()

  def CanComputeHeight(self):
    raise Unimplemented()

class HorizontallyFixedRelativeLayout(BaseRelativeLayout):
  def __init__(self, width):
    BaseRelativeLayout.__init__(self)
    self.width = width
    self.height = None
    self._computing_height = False

  def InvalidateSize(self):
    assert not self._computing_height
    self.height = None
    self.SetPositions()

  def GetWidth(self):
    return self.width

  def GetHeight(self):
    if self.height is None:
      self._ComputeHeight()
    return self.height

  def CanComputeWidth(self):
    return True

  def CanComputeHeight(self):
    log_gui("CanComputeHeight:",self,self.height,self._computing_height)
    if self.height is None and self._computing_height:
      log_gui("Return false")
      return False
    else:
      log_gui("Return true")
      return True

  def _ComputeHeight(self):
    # Keep us from recursively calling this method:
    if self._computing_height:
      log_gui(self)
      raise AlreadyComputingHeight()
    log_gui("Start computing height")

    self._computing_height = True
    max_height = 0
    for dp_item in self.dp_items:
      # Don't consider height of anything that depends on the height of this
      # layout.
      if not dp_item.HasUnresolvedDependencies():
        log_gui("dp item", dp_item, "does not have unresolved dependencies.")
        self.height = max(self.height, dp_item.GetTop() + dp_item.GetHeight())
      elif self.height is None:
        try:
          max_height = max(max_height, dp_item.GetHeight())
        except AlreadyComputingHeight:
          pass
    if self.height is None:
      self.height = self.SearchForHeight(1, 1, 480)
      log_gui("Search for height returns", self.height)

    self._computing_height = False

  def SearchForHeight(self, current, min, max):
    temp = self.height
    self.height = current
    top, bottom = self.FindBounds()
    self.height = temp

    if top < 0 or bottom > current:
      # Needs to be bigger
      nextval = int((current + max) / 2)
      if nextval == current or current == max:
        return current
      return self.SearchForHeight(nextval, current + 1, max)

    if top > 0 and bottom < current:
      # Can be smaller
      nextval = int((current + min) / 2)
      if nextval == current or current == min:
        return current
      return self.SearchForHeight(nextval, min, current)

    # perfect!
    return current

  def FindBounds(self):
    # Find the top of the highest and bottom of the lowest item. Return as
    # a tuple.
    highest = None
    lowest = None
    for dp_item in self.dp_items:
      top = dp_item.GetTop()
      bottom = dp_item.GetHeight() + top
      log_gui("Find bounds for current height", self.height, ":",top,bottom)
      if highest is None or top < highest:
        highest = top
      if lowest is None or bottom > lowest:
        lowest = bottom
    return highest, lowest
